#include <sys/stat.h>
#include <sys/types.h>
#include "CloudiskServer.h"
#include "CryptoUtil.h"
#include "User.h"
#include "ServerUtil.h"
#include "signup.srpc.h"
#include "signin.srpc.h"

using namespace std;
using std::cout;
using namespace wfrest;
using namespace protocol;
using nlohmann::json;
using namespace nlohmann::literals;
using namespace srpc;

static const string MYSQL_URL = "mysql://root:123@localhost/new";
static const string REDIRECT_URL = "/static/view/home.html";

void CloudiskServer::setProducer(string &uri, string &exchange, string &routingKey)
{
    m_producer = shared_ptr<MqProducer>(new MqProducer{uri, exchange, routingKey});

}

void CloudiskServer::register_modules()
{
    // 设置静态资源的路由
    register_static_resources_module();
    register_signup_module();
    register_signin_module();
    register_userinfo_module();
    register_fileupload_module();
    register_filelist_module();
    register_filedownload_module();
}

void CloudiskServer::register_static_resources_module()
{
    m_server.GET("/", [](const HttpReq *, HttpResp * resp){
        resp->File("static/view/signin.html");
    });

    m_server.GET("/user/signup", [](const HttpReq *, HttpResp * resp){
        resp->File("static/view/signup.html");
    });

    m_server.GET("/user/signin", [](const HttpReq *, HttpResp * resp){
        resp->File("static/view/signin.html");
    });
    
    m_server.GET("/static/view/signin.html", [](const HttpReq *, HttpResp * resp){
        resp->File("static/view/signin.html");
    });

    m_server.GET("/static/view/home.html", [](const HttpReq *, HttpResp * resp){
        resp->File("static/view/home.html");
    });

    m_server.GET("/static/js/auth.js", [](const HttpReq *, HttpResp * resp){
        resp->File("static/js/auth.js");
    });

    m_server.GET("/static/img/avatar.jpeg", [](const HttpReq *, HttpResp * resp){
        resp->File("static/img/avatar.jpeg");
    });

    m_server.GET("/file/upload", [](const HttpReq *, HttpResp * resp){
        resp->File("static/view/index.html");
    });

    m_server.Static("/file/upload_files","static/view/upload_files");
}


/********************************************************************************
 *                                      注册模块
 *******************************************************************************/
void CloudiskServer::register_signup_module()
{
    // seriesHandler
    m_server.POST("/user/signup", [](const HttpReq* req, HttpResp* resp, SeriesWork* series) 
    {
        // 1. 校验请求参数，解析请求参数
        if (req->content_type() != APPLICATION_URLENCODED) {
            resp->set_status(HttpStatusBadRequest);
            resp->String("<html>400 Bad Request</html>");
        }

        auto& params = req->form_kv();
        User user;
        user.username = params["username"];
        user.password = params["password"];

#ifdef DEBUG
        cout << "[DEBUG] " << "username: " << user.username << endl;
        cout << "[DEBUG] " << "password: " << user.password << endl;
#endif

        // 询问注册中心，找到能用的服务的ip和端口
        WFHttpTask *httpTask = WFTaskFactory::create_http_task(
            "http://192.168.201.130:8500/v1/health/service/RegisterService?passing=true",
            0,
            0,
            [user, resp](WFHttpTask *task)
            {
                int state = task->get_state();
                int error = task->get_error();
                if(state != WFT_STATE_SUCCESS)
                {
                    return ServerUtil::setError(
                        resp, 
                        HttpStatusBadRequest, 
                        WFGlobal::get_error_string(state, error)
                    );
                }

                const void *body;
                size_t size;
                task->get_resp()->get_parsed_body(&body, &size);

                json RegisterServiceData = json::parse(string{(const char *)body});

                string ip = RegisterServiceData[0]["Service"]["TaggedAddresses"]["lan_ipv4"]["Address"];
                unsigned short port = RegisterServiceData[0]["Service"]["TaggedAddresses"]["lan_ipv4"]["Port"];

#ifdef DEBUG
                cout << "[DEBUG] " << "ip = " << ip << ", port = "
                     << port << endl;
#endif

                // 将用户名和密码转交给注册服务端
	            Signup::SRPCClient client(ip.c_str(), port);    

    	        RegisterRequest request;
	            request.set_username(user.username);
	            request.set_password(user.password);
        
                srpc::SRPCClientTask *srpcTask = client.create_Register_task(
                [resp](RegisterResponse *response, srpc::RPCContext *context)
                {        	
                    string message = response->message();
#ifdef DEBUG
	                cout << "[DEBUG] " << message << endl;
#endif

                    // 任务执行失败
                    if (!context->success())
                    {
                        message = string("RPC failed: ") + context->get_errmsg();
#ifdef DEBUG
                        cerr << "[ERROR] " << message << endl;
#endif
                        return ServerUtil::setError(
                            resp, 
                            HttpStatusServiceUnavailable, 
                            message
                        ); 
                    }    
                    
                    // 用户名已存在
                    message = response->message();
#ifdef DEBUG
                    cout << "[DEBUG] response message: " << message << endl;
#endif
                    if (message == "FAIL")
                    {
                        return ServerUtil::setError(
                            resp, 
                            HttpStatusConflict, 
                            "username already exists"
                        );
                    }

                    // 成功
                    return resp->String(message);
                });

        	    srpcTask->serialize_input(&request);
                series_of(task)->push_back(srpcTask);
            }
        );
        series->push_back(httpTask);
    });
}

/********************************************************************************
 *                                      登录模块
 *******************************************************************************/

void CloudiskServer::register_signin_module()
{
    m_server.POST("/user/signin",[](const HttpReq *req, HttpResp *resp, SeriesWork* series)
    {
        if(req->content_type() != APPLICATION_URLENCODED)
        {
            resp->set_status(HttpStatusBadRequest);
            resp->String("<html>400 Bad Request</html>");
            return;
        }

        map<string,string> &params = req->form_kv();
        User user;

        user.username = params.at("username");
        user.password = params.at("password");
#ifdef DEBUG
        cout << "[DEBUG] " << "username: " << user.username << endl;
        cout << "[DEBUG] " << "password: " << user.password << endl;
#endif

        // 查询注册中心，得到可用的登录服务的ip和端口
        WFHttpTask *httpTask = WFTaskFactory::create_http_task(
            "http://192.168.201.130:8500/v1/health/service/LoginService?passing=true",
            0,
            0,
            [user, resp](WFHttpTask *task)
            {
                int state = task->get_state();
                int error = task->get_error();
                if(state != WFT_STATE_SUCCESS)
                {
                    return ServerUtil::setError(
                        resp, 
                        HttpStatusBadRequest, 
                        WFGlobal::get_error_string(state, error)
                    );
                }

                const void *body;
                size_t size;
                task->get_resp()->get_parsed_body(&body, &size);

                json RegisterServiceData = json::parse(string{(const char *)body});

                string ip = RegisterServiceData[0]["Service"]["TaggedAddresses"]["lan_ipv4"]["Address"];
                unsigned short port = RegisterServiceData[0]["Service"]["TaggedAddresses"]["lan_ipv4"]["Port"];

#ifdef DEBUG
                cout << "[DEBUG] " << "ip = " << ip << ", port = "
                     << port << endl;
#endif

                // 将用户名和密码转交给登录服务端
                Signin::SRPCClient client(ip.c_str(), port);

                LoginRequest request;
                request.set_username(user.username);
                request.set_password(user.password);

                srpc::SRPCClientTask *srpcTask = client.create_Login_task(
                [resp, user](LoginResponse *response, srpc::RPCContext *context)
                {
                    string message = response->message();
#ifdef DEBUG
	                cout << "[DEBUG] " << message << endl;
#endif

                    if(!context->success() || message == "FAIL")
                    {
#ifdef DEBUG
                        cerr << "[ERROR] " << context->get_errmsg() << endl; 
#endif
                        return ServerUtil::setError(
                            resp, 
                            HttpStatusBadRequest, 
                            "password error"
                        );
                    }

                    // 任务失败
                    if (!context->success())
                    {
                        message = string("RPC failed: ") + context->get_errmsg();
#ifdef DEBUG
                        cerr << "[ERROR] " << message << endl;
#endif
                        return ServerUtil::setError(
                            resp, 
                            HttpStatusServiceUnavailable, 
                            message
                        ); 
                    }

                    // 用户名或密码错误
                    if (message == "FAIL") {
                        return ServerUtil::setError(
                            resp, 
                            HttpStatusUnauthorized, 
                            "invalid username or password"
                        );
                    }

                    // 成功，返回token
                    string token = CryptoUtil::generate_token(user);
                    json userData = {
                        {
                            "data",
                            {
                                {"Username", user.username},
                                {"Token", token},
                                {"Location", REDIRECT_URL}
                            }
                        }
                    };        
#ifdef DEBUG
                    cout << "[DEBUG]" << endl << userData.dump(4) << endl;
#endif
                    resp->String(userData.dump());
                });
            srpcTask->serialize_input(&request);
            series_of(task)->push_back(srpcTask);
            });
        series->push_back(httpTask);
    });
}

/********************************************************************************
 *                                获取用户信息模块
 *******************************************************************************/

void CloudiskServer::register_userinfo_module()
{
    m_server.GET("/user/info", [](const HttpReq *req, HttpResp *resp)
    {
        const map<string, string> &queryList = req->query_list();
        if (!queryList.count("username") || !queryList.count("token"))
        {
            return ServerUtil::setError(
                resp, 
                HttpStatusBadRequest, 
                "missing query params"
            );
        }

        User user;
        user.username = queryList.at("username");
        string token = queryList.at("token");

#ifdef DEBUG
            cout << "[DEBUG] " << "username: " << user.username << endl;
            cout << "[DEBUG] " << "token: " << token << endl;
#endif
        
        if(token.empty() || !CryptoUtil::varify_token(token, user))
        {
            return ServerUtil::setError(
                resp, 
                HttpStatusUnauthorized, 
                "token error"
            );
        }
        string query = "SELECT created_at FROM tbl_user WHERE username='"
                       + user.username + "';";
         
#ifdef DEBUG
        cout << "[DEBUG] " << query << endl;
#endif

        resp->MySQL(MYSQL_URL, query, [resp, user](MySQLResultCursor *cursor) mutable
        {
            if(cursor->get_cursor_status() != MYSQL_STATUS_GET_RESULT || cursor->get_rows_count() != 1)
            {
                return ServerUtil::setError(
                    resp, 
                    HttpStatusBadRequest, 
                    "select sql error!"
                );
            }

            vector<MySQLCell> record;
            cursor->fetch_row(record);
            user.createAt = record[0].as_string();

#ifdef DEBUG
            cout << "[DEBUG] " << "createAt: " << user.createAt << endl;
#endif

            json userData {
                {
                    "data",
                    {
                        {"Username", user.username},
                        {"SignupAt", user.createAt}
                    }
                }
            };

#ifdef DEBUG
            cout << "[DEBUG] " << endl << userData.dump(4) << endl;
#endif

            resp->String(userData.dump());            
        });
    });

}

/********************************************************************************
 *                               获取文件列表模块
 *******************************************************************************/

void CloudiskServer::register_filelist_module()
{
    m_server.POST("/file/query", [](const HttpReq *req, HttpResp *resp)
    {
        if (req->content_type() != APPLICATION_URLENCODED) {    
            return ServerUtil::setError(
                resp, 
                HttpStatusBadRequest, 
                "invalid content-type"
            );
        }

        const map<string, string>&queryList = req->query_list();
        if (!queryList.count("username") || !queryList.count("token"))
        {
            return ServerUtil::setError(
                resp, 
                HttpStatusBadRequest, 
                "missing query params"
            );
        }

        User user;
        string token;
        map<string, string>param = req->form_kv();
        user.username = queryList.at("username");
        token = queryList.at("token");

#ifdef DEBUG
            cout << "[DEBUG] " << "username: " << user.username << endl;
            cout << "[DEBUG] " << "token: " << token << endl;
            cout << "[DEBUG] " << "limit: " << param.at("limit") << endl;
#endif

        if(token.empty() || !CryptoUtil::varify_token(token, user))
        {
            return ServerUtil::setError(
                resp, 
                HttpStatusUnauthorized, 
                "invalid token"
            );
        }

        // 利用用户名查询用户id，再根据用户id查询文件表

        string query = "SELECT tbl_file.filename, tbl_file.hashcode, "
                       "tbl_file.size, tbl_file.created_at, tbl_file.last_update, "
                       "tbl_file.status FROM tbl_user, tbl_file "
                       "WHERE tbl_user.id = tbl_file.uid AND tbl_user.username = '" 
                       + user.username + "' LIMIT " + param.at("limit") + ";";

#ifdef DEBUG
        cout << "[DEBUG] " << query << endl;
#endif

        resp->MySQL(MYSQL_URL, query, [resp, param](MySQLResultCursor *cursor) mutable
        {
            if(cursor->get_cursor_status() != MYSQL_STATUS_GET_RESULT)
            {
                return ServerUtil::setError(
                    resp, 
                    HttpStatusBadRequest, 
                    "select file info SQL error"
                );
            }

            vector<MySQLCell> record;
            json fileData = json::array();

            // 遍历查询结果
            while(cursor->fetch_row(record))
            {
                // status = 0 时代表文件可获取
                if(record[5].as_int() == 0){
                    fileData.push_back(
                        {
                            {"FileHash", record[1].as_string()},
                            {"FileName", record[0].as_string()},
                            {"FileSize", record[2].as_ulonglong()},
                            {"UploadAt", record[3].as_string()},
                            {"LastUpdated", record[4].as_string()}
                        });
#ifdef DEBUG
                        cout << "[DEBUG] " << "filename: " << record[0].as_string() << endl;
#endif
                    }
            }

#ifdef DEBUG
                        cout << "[DEBUG]" << endl;
                        cout << fileData.dump(4) << endl;
#endif
            
            resp->String(fileData.dump());
        });
    });
}

/********************************************************************************
 *                                 上传文件模块
 *******************************************************************************/

void CloudiskServer::register_fileupload_module()
{
    m_server.POST("/file/upload", [this](const HttpReq *req, HttpResp *resp)
    {
        if(req->content_type() != MULTIPART_FORM_DATA)
        {
            return ServerUtil::setError(
                resp, 
                HttpStatusBadRequest, 
                "invalid content-type"
            );
        }
        const map<string, string>&queryList = req->query_list();
        if(!queryList.count("username") || !queryList.count("token"))
        {
            return ServerUtil::setError(
                resp, 
                HttpStatusBadRequest, 
                "missing query params"
            );
        }

        User user;     
        user.username = queryList.at("username");
        string token = queryList.at("token");

#ifdef DEBUG
            cout << "[DEBUG] " << "username: " << user.username << endl;
            cout << "[DEBUG] " << "token: " << token << endl;
#endif

        if(token.empty() || !CryptoUtil::varify_token(token, user))
        {
            return ServerUtil::setError(
                resp, 
                HttpStatusUnauthorized, 
                "invalid token"
            );            
        }

        const auto &form = req->form();
        if(form.find("file") == form.end())
        {
            return ServerUtil::setError(
                resp, 
                HttpStatusBadRequest, 
                "no file found"
            );
        }       
        
        const auto &fileInfo = form.at("file");
        string fileName = PathUtil::base(fileInfo.first);
        string content = fileInfo.second;
        if(fileName.empty() || content.empty())
        {
            return ServerUtil::setError(
                resp, 
                HttpStatusBadRequest, 
                "empty filename or content"
            );
        }

        size_t fileSize = content.size();
        string hashCode = CryptoUtil::hashFile(content);

#ifdef DEBUG
        cout << "[DEBUG] " << "filename: " << fileName << endl;
        cout << "[DEBUG] " << "filesize: " << fileSize << endl;
#endif

        // 查看结果是否为0，若不为0则代表用户已经上传过此文件，此时比对哈希值
        // 若哈希值相等则直接重定向，若不相等则用update更新hash值并save
        // 若结果为0，则先insert再save

        string query = "SELECT tbl_file.uid, tbl_file.hashcode "
                       "FROM tbl_user, tbl_file "
                       "WHERE tbl_user.id = tbl_file.uid "
                       "AND tbl_user.username = '" + user.username + "' "
                       "AND tbl_file.filename = '" + fileName + "';";

#ifdef DEBUG
        cout << "[DEBUG] " << query << endl;
#endif

        string path = "File/" + user.username;
        if(access(path.c_str(), F_OK) == -1)
        {
            assert(mkdir(path.c_str(), 0777) != -1 && "mkdir error");
        }

        resp->MySQL(MYSQL_URL, query, [this, resp, hashCode, fileName, fileSize, user, path, content]
            (MySQLResultCursor *cursor) mutable
            {
                if(cursor->get_cursor_status() == MYSQL_STATUS_ERROR)
                {
                    return ServerUtil::setError(
                        resp, 
                        HttpStatusBadRequest, 
                        "sql error on select"
                    );
                }
                if(cursor->get_rows_count() == 0)
                {
                    // 文件不存在，上传并插表
                    string path_r = path + "/" +fileName;
                    resp->Save(path_r, std::move(content));
                    m_producer->PutMsg(path_r);

                    string query = "SELECT id FROM tbl_user WHERE username='" 
                                   + user.username + "';";
                    
#ifdef DEBUG
                    cout << "[DEBUG] " << query << endl;
#endif

                    resp->MySQL(MYSQL_URL, query, [resp, hashCode, fileName, fileSize]
                        (MySQLResultCursor *cursor)
                        {
                            if(cursor->get_cursor_status() != MYSQL_STATUS_GET_RESULT|| cursor->get_rows_count() != 1)
                            {
                                return ServerUtil::setError(
                                    resp, 
                                    HttpStatusBadRequest, 
                                    "sql error on get user id"
                                );
                            }

                            vector<MySQLCell> record;
                            cursor->fetch_row(record);
                            int userId = record[0].as_int();

                            string query = "INSERT INTO tbl_file(uid, filename, hashcode, size) "
                                   "VALUES(" + to_string(userId) + ", '" 
                                   + fileName + "', '" + hashCode + "', " 
                                   + to_string(fileSize) + ");";
                    
#ifdef DEBUG
                            cout << "[DEBUG] " << query << endl;
#endif

                            resp->MySQL(MYSQL_URL, query, [resp](MySQLResultCursor *cursor)
                            {
                                if(cursor->get_cursor_status() != MYSQL_STATUS_OK || cursor->get_affected_rows() != 1)
                                {
                                    return ServerUtil::setError(
                                        resp, 
                                        HttpStatusBadRequest, 
                                        "insert fileinfo failed"
                                    );
                                }
                                resp->set_status(HttpStatusSeeOther);
                                resp->add_header_pair("Location", REDIRECT_URL);
                            });
                        });
                }else
                {
                    // 文件存在，比对hash
                    vector<MySQLCell> record;
                    cursor->fetch_row(record);
                    string oldHash = record[1].as_string();
                    int userId     = record[0].as_int();

                    if(oldHash == hashCode)
                    {
                        // hash相同，直接返回
                        resp->set_status(HttpStatusSeeOther);
                        resp->add_header_pair("Location", REDIRECT_URL);
                    }else
                    {
                        // hash不同，替换文件
                        string path_r = path + "/" +fileName;
                        resp->Save(path_r, std::move(content));
                        m_producer->PutMsg(path_r);

                        string query = "UPDATE tbl_file SET size=" + to_string(fileSize) 
                                     + ", hashcode='" + hashCode + "' "
                                     + "WHERE uid=" + to_string(userId) + " "
                                     + "AND filename='" + fileName + "';";

#ifdef DEBUG
                        cout << "[DEBUG] " << query << endl;
#endif

                        resp->MySQL(MYSQL_URL, query, [resp](MySQLResultCursor *cursor)
                        {
                            if(cursor->get_cursor_status() != MYSQL_STATUS_OK || cursor->get_affected_rows() != 1)
                            {
                                return ServerUtil::setError(
                                    resp, 
                                    HttpStatusBadRequest, 
                                    "update fileinfo failed"
                                );
                            }
                            resp->set_status(HttpStatusSeeOther);
                            resp->add_header_pair("Location", REDIRECT_URL);                           
                        });
                    }
                }
            });        
    });
}

/********************************************************************************
 *                                  下载文件模块
 *******************************************************************************/

void CloudiskServer::register_filedownload_module()
{
    m_server.GET("/file/download", [](const HttpReq *req, HttpResp *resp)
    {
        const map<string, string> &queryList = req->query_list();

        if (!queryList.count("username") || !queryList.count("token") ||
            !queryList.count("filename") || !queryList.count("filehash"))
            return ServerUtil::setError(
                resp, 
                HttpStatusBadRequest, 
                "missing query params"
            );

        User user;
        user.username = queryList.at("username");
        string fileName = queryList.at("filename");
        string fileHash = queryList.at("filehash");
        string token = queryList.at("token");

#ifdef DEBUG
            cout << "[DEBUG] " << "username: " << user.username << endl;
            cout << "[DEBUG] " << "token: " << token << endl;
#endif

        if(token.empty() || !CryptoUtil::varify_token(token, user))
        {
            return ServerUtil::setError(
                resp, 
                HttpStatusUnauthorized, 
                "invalid token"
            );
        }

        // 校验用户文件是否在数据库中存在
        string query = "SELECT tbl_file.hashcode, tbl_file.status "
                       "FROM tbl_user, tbl_file "
                       "WHERE tbl_user.id = tbl_file.uid "
                       "AND tbl_user.username = '" + user.username + "' "
                       "AND tbl_file.hashcode = '" + fileHash + "';";

#ifdef DEBUG
        cout << "[DEBUG] " << query << endl;
#endif

        resp->MySQL(MYSQL_URL, query, [resp, user, fileName, fileHash](MySQLResultCursor *cursor)
        {
            
            if(cursor->get_cursor_status() != MYSQL_STATUS_GET_RESULT)
            {
                return ServerUtil::setError(
                    resp, 
                    HttpStatusBadRequest, 
                    "select sql failed!"
                );
            }

            vector<MySQLCell> record;
            cursor->fetch_row(record);

            int status = record[1].as_int();

#ifdef DEBUG
            cout << "status: " << status << endl;
#endif
            if (status != 0)
            {
                return ServerUtil::setError(
                    resp, 
                    HttpStatusNotFound, 
                    "file unused!"
                );
            }
            
            resp->File("File/" + user.username + "/" + fileName);
        });
    });
}

